#ifndef __LIO3D_H_
#define __LIO3D_H_

#include "liomath.h"
#include "lioimg.h"

#ifdef _UNICODE
#define GL_CHAR_SIZE GL_UNSIGNED_SHORT
#else
#define GL_CHAR_SIZE GL_UNSIGNED_BYTE
#endif

#define FONT_FACE_SIZE 32

#define ALIGN_ADJUST -1
#define ALIGN_NONE 0

namespace Lio {

#ifdef _DEBUG

DECSRC(glBeginIndex);
#define glBegin SETSRC(glBegin,glBeginIndex)
#define   glEnd CLRSRC(glEnd,glBeginIndex)

DECSRC(glNewListIndex);
#define glNewList SETSRC(glNewList,glNewListIndex)
#define glEndList CLRSRC(glEndList,glNewListIndex)

#endif

enum FONT_TYPE {FONT_BITMAP,FONT_OUTLINE,FONT_TEXTURE};

struct GLScreen
{
	float	angle,
			ratio,
			left,
			right,
			bottom,
			top,
			zNearPers,
			zFarPers,
			zNearOrtho,
			zFarOrtho;
};

CSTR GlErrorToStr(GLenum error);

class Texture
{
protected:
	GLuint				id; // GL Texture Identification
public:
	BOOL				mipmapped;
	GLint				width; // For Not Mipmapped Must be 2^n + 2(border) for some integer n. 
	GLint				height;	// For Not Mipmapped Must be 2^m + 2(border) for some integer m. 
	GLint				levelDetail; // The level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image. 
    GLint				childs; // The number of color childs in the texture. Must be 1, 2, 3, or 4. 	
	GLint				border;	// The width of the border. Must be either 0 or 1. 
    GLuint				format; // GL_COLOR_INDEX, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT,GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE,GL_LUMINANCE_ALPHA, GL_BGR_EXT, GL_BGRA_EXT 
    GLuint				dataFormat; // GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT	
    BYTE				*data; // image data
	GLint				magFilter; // Mag Filter To Use (GL_NEAREST, GL_LINEAR)
	GLint				minFilter; // Min Filter To Use (GL_NEAREST, GL_LINEAR)
	GLint				wrap; // Clamped or Repeated (GL_CLAMP, GL_REPEAT)
	int					error;

	Texture():
		width(0),height(0),id(0),
		minFilter(GL_LINEAR),magFilter(GL_LINEAR),wrap(GL_REPEAT),
		childs(3),
		data(NULL),dataFormat(GL_UNSIGNED_BYTE),format(GL_RGB)
	{}
	~Texture()
	{
		destroy();
	}
	BOOL create(Image * image);
	void destroy();
	void update()
	{
		glTexSubImage2D (	GL_TEXTURE_2D,
							levelDetail,
							0, 0,
							width, height,
							format,
							dataFormat,
							data);
	}
	void bind() const {glBindTexture(GL_TEXTURE_2D, id);}
	int getID() const {return id;}
};

class GLObject
{
protected:
	GLuint list; // Display List
	HDC hDC; // Device Context
public:
	GLenum error;
	GLObject():list(0),error(GL_NO_ERROR),hDC(NULL){}
	virtual BOOL create(HDC	hDC)=NULL;
	virtual void destroy()=NULL;
};

class DisplayList:public GLObject
{
#ifdef _DEBUG
	BOOL compilation;
#endif
public:
	DisplayList()
	{
		_DEBUG_EXPR(compilation=FALSE);
	}
	~DisplayList(){destroy();}
	BOOL create(HDC)
	{
		destroy();
		return generate();
	}
	BOOL generate()
	{
		assert(!compilation);
		assert(!list);
		list=glGenLists(1);
		error=glGetError();
		return error==GL_NO_ERROR;
	}
	void destroy()
	{
		assert(!compilation);
		if(!list)return;
		glDeleteLists(list,1);
		list=0;
	}
	BOOL start()
	{
		if(list||generate())
		{
			glNewList(list,GL_COMPILE);
			error=glGetError();
			if(error==GL_NO_ERROR)
			{
				_DEBUG_EXPR(compilation=TRUE);
				return TRUE;
			}
		}
		assert(!compilation);
		return FALSE;
	}
	BOOL end()
	{
		if(list)
		{
			assert(compilation);
			_DEBUG_EXPR(compilation=FALSE);
			glEndList();
			error=glGetError();
			if(error==GL_NO_ERROR)
				return TRUE;
		}
		assert(!compilation);
		return FALSE;
	}
	void execute()
	{
		if(list&&!error)
			glCallList(list);
	}
	operator GLuint(){return list;}
};
class Transform
{
public:
	GLint view[4];
	GLdouble model[16],project[16];
	void update()
	{
		glGetIntegerv(GL_VIEWPORT, view);
		glGetDoublev(GL_MODELVIEW_MATRIX, model);
		glGetDoublev(GL_PROJECTION_MATRIX, project);
	}
	void getObjectCoor(double winX, double winY, double winZ, double obj[3])
	{
		gluUnProject(
			winX,winY,winZ,
			model,project,view,
			obj,obj+1,obj+2);
	}
	void getWindowCoor(double objX, double objY, double objZ, double win[3])
	{
		gluProject(
			objX,objY,objZ,
			model,project,view,
			win,win+1,win+2);
	}
	void getWindowDepth(LONG winX, LONG winY, float *winZ)
	{
		glReadPixels(winX, winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, winZ);
	}
};
class CompileDisplayList
{
protected:
	DisplayList *_list;
public:
	CompileDisplayList(DisplayList *list)
	{
		if(list&&list->start())
		{
			_list=list;
		}
		else
		{
			_list=NULL;
			assert(FALSE);
		}
	}
	~CompileDisplayList()
	{
		if(_list&&!_list->end())
		{
			CSTR err_msg=GlErrorToStr(_list->error);
			assert(FALSE);
			throw err_msg;
		}
	}
};

class Font:public GLObject
{
protected:
	Texture				texture; // Texture for textured font
public:
	enum TextAlign {
		ALIGN_LEFT,
		ALIGN_CENTER,
		ALIGN_RIGHT
	};
	int					height; // Font Height (Based On Character (-) Or Cell (+) For Windows Fonts)
	int					width; // Font Width (0 For Automatic Match For Windows Fonts)
	int					first; // First character
	int					count; // Number Of Characters
	float				x,y,z,zoom;
	int					align;
	DWORD				color;
	int					expand;
	int					type; // Outlined, Bitmap Or Texture Font
	// Bitmap Font:
	_TCHAR				face[FONT_FACE_SIZE]; // Font Name
	DWORD				charset; // ANSI_CHARSET, DEFAULT_CHARSET, SYMBOL_CHARSET, SHIFTJIS_CHARSET, HANGEUL_CHARSET, HANGUL_CHARSET, GB2312_CHARSET, CHINESEBIG5_CHARSET, OEM_CHARSET, JOHAB_CHARSET, HEBREW_CHARSET, ARABIC_CHARSET, GREEK_CHARSET, TURKISH_CHARSET, VIETNAMESE_CHARSET, THAI_CHARSET, EASTEUROPE_CHARSET, RUSSIAN_CHARSET, MAC_CHARSET, BALTIC_CHARSET
	BYTE				quality; // ANTIALIASED_QUALITY, CLEARTYPE_QUALITY, DEFAULT_QUALITY, DRAFT_QUALITY, NONANTIALIASED_QUALITY, PROOF_QUALITY
	BOOL				bold;
	BOOL				italic;
	BOOL				underline;
	BOOL				strike;
	int					escapement; // Angle Of Escapement
	int					orientation; // Base-line orientation angle
	TEXTMETRIC			textMetrix; // font size data
	INT					*charWidths; // Individual widths of the chars
	BOOL				loadMetrix; // Specify If Metrix Must Be Loaded
	// Outlined Font:
	BOOL				polygones; // Specifies the format, either WGL_FONT_LINES (0) or WGL_FONT_POLYGONS (1), to use in the display lists. When format is WGL_FONT_LINES, the wglUseFontOutlines function creates fonts with line segments. When format is WGL_FONT_POLYGONS, wglUseFontOutlines creates fonts with polygons.
	float				deviation; // Specifies the maximum chordal deviation from the original outlines. When deviation is zero, the chordal deviation is equivalent to one design unit of the original font. The value of deviation must be equal to or greater than 0.
	float				depth; // Specifies how much a font is extruded in the negative z direction. The value must be equal to or greater than 0. When extrusion is 0, the display lists are not extruded.
	GLYPHMETRICSFLOAT	*glyphMetrix; // Points to an array of count GLYPHMETRICSFLOAT structures that is to receive the metrics of the glyphs. When lpgmf is NULL, no glyph metrics are returned
	// Textured Font:
	int					colls; // Number Of Characters Per Row
	int					rows; // Number Of Characters Per Collumn
	float				shift; // Shift Between Successive Letters
	GLuint				textureID; // Texture For Textured Fonts
	Image				*textureSource;
	
	Font();
	~Font();

	void setFace(CSTR newFace);
	BOOL create(HDC	hDC);
	void destroy();

	float getTextWidth(CSTR text, int index = 0, int length = -1);
	int getTextIndex(CSTR text, float width, int index = 0, int length = -1);
	BOOL print(CSTR text, int index = 0, int length = -1);

	BOOL loaded() const {return list;}
	BOOL setTextureSource(CSTR bmpFileName);
	void setPosition(float newX, float newY, float newZ = 0){x=newX;y=newY;z=newZ;}
};

void glPrint(Font *font,CSTR fmt, ...);

// Get the line width parameters: min, max and return granularity
GLfloat getLineWidthProps(GLfloat *min, GLfloat *max);

GLvoid drawParallel(
				float a = 1.0, float b = 1.0, float c = 1.0, // Sides For X,Y and Z Axes
				float u = 1.0, float v = 1.0, // Texture Coordinates
				float n = 1.0); // Normal
GLvoid drawEllipsoid(
				float a = 1.0, float b = 1.0, float c = 1.0,
				int nLat = 16, int nLon = 32);
GLvoid drawSphere(float R = 1.0, int nLat = 16, int nLon = 32);
GLvoid drawEllipse(float x = 0,float y = 0,float a = 1,float b = 1, int n = 16, BOOL fill = FALSE);
void drawLine(
		float startX, float startY, float startZ,
		float endX, float endY, float endZ,
		DWORD color = 0, float line = 0);
void drawLine2(
		float startX, float startY,
		float endX, float endY,
		DWORD color = 0, float line = 0);
void drawBox(
		float left, float top,
		float width, float height,
		DWORD color = 0, float line = 0);
void fillBox(
		float left, float top,
		float width, float height,
		DWORD color = 0);
inline GLvoid drawCercle(float x = 0, float y = 0, float R = 1, int n = 16)
{
	drawEllipse(x,y,R,R,n,FALSE);
}
inline GLvoid fillCercle(float x = 0, float y = 0, float R = 1, int n = 16)
{
	drawEllipse(x,y,R,R,n,TRUE);
}
void drawLineIndicator(
		float left,float top,
		float width, float height,
		float ratio, float line, int direction = DIR_RIGHT,
		int startColor = 0, int  middleColor = 0, int  endColor = 0,
		int bgColor = 0, int  frameColor = 0);
void drawGraphIndicator(
		float left,float top,
		float width, float height,
		float *dataX, float *dataY, int n,
		float minX = 0, float maxX = 0, float minY = 0, float maxY = 0, float line = 1.0f,
		int graphColor = 0, int bgColor = 0, int frameColor = 0, int gridColor = 0, int markColor = 0,
		int gridx = 0, int gridy = 0, float grid = 1.0f, int mark = -1);
void drawGraphIndicator(
		float left,float top,
		float width, float height,
		float xStart, float xEnd, float *data, int n,
		float minX = 0, float maxX = 0, float minY = 0, float maxY = 0,float line = 1.0f,
		int graphColor = 0, int bgColor = 0, int frameColor = 0, int gridColor = 0, int markColor = 0,
		int gridx = 0, int gridy = 0, float grid = 1.0f, int mark = -1);
void drawMapIndicator(
		float left,float top,
		float width, float height,
		float ratioX, float ratioY, float line = 1.0f,
		int markColor = 0, int bgColor = 0, int frameColor = 0, int gridColor = 0,
		int gridx = 0, int gridy = 0, float grid = 1.0f);
void drawCercleIndicator(
		float cx,float cy,
		float x, float y,
		float R = 0, float line = 1.0f,
		int markColor = 0, int frameColor = 0, int fillColor = 0,
		BOOL cercle = TRUE, int n = 16, BOOL norm = TRUE);
void drawSphereIndicator(
		float cx, float cy , float cz,
		float x, float y, float z,
		float R = 0, float line = 1.0f,
		int markColor = 0, int frameColor = 0, int fillColor = 0,
		BOOL cercle = FALSE, int n = 16 ,BOOL norm = TRUE);
void rotObject(CMATRIX3 R, int count, float vertices[][3][3], float out[][3][3] = NULL);
void rotObject(CQUAT q, int count, float vertices[][3][3], float out[][3][3] = NULL);
void transObject(float T[3], int count, float vertices[][3][3], float out[][3][3] = NULL);
BOOL setMemAlign(int memWidth, BOOL pack);
int getMemAlign(BOOL pack);
GLuint getPixelFormat(const Image * image);
void drawImage(const Image * image, float x = 0, float y = 0, int align = ALIGN_NONE);
void readImage(Image * image, float x = 0, float y = 0, int align = ALIGN_NONE);

void printMatrix( Font *matrixFont,
				  CMATRIX m, int rows = 3, int cols = 3,
				  CSTR name = NULL,
				  float x0 = 10, float y0 = 10,
				  float dx = 100, float dy = 20,
				  Font *titleFont = NULL,
				  CSTR format = _T("%f"),
				  int colorPeriod = 3,
				  DWORD color0 = INT_WGRAY, DWORD color1 = INT_YELLOW, DWORD color2 = INT_WHITE, DWORD color3 = INT_RED);

inline void setColor(DWORD color)
{
	glColor4ubv(INT_TO_BYTE(color));
}
void setClearColor(DWORD color);
void createLight(GLenum id, GLfloat *ambient, GLfloat *diffuse, GLfloat *position, GLfloat *specular = FALSE, GLfloat *attenuation = FALSE);
inline void glRotate(float anglex, float angley, float anglez, BOOL clockwise = TRUE)
{
	if(clockwise)
	{
		if(anglex)glRotatef(anglex,1.0f,0.0f,0.0f);
		if(angley)glRotatef(angley,0.0f,1.0f,0.0f);
		if(anglez)glRotatef(anglez,0.0f,0.0f,1.0f);
	}
	else
	{
		if(anglez)glRotatef(-anglez,0.0f,0.0f,1.0f);
		if(angley)glRotatef(-angley,0.0f,1.0f,0.0f);
		if(anglex)glRotatef(-anglex,1.0f,0.0f,0.0f);
	}
}
void setLight(BOOL light = TRUE);
void setSmooth(BOOL smooth = TRUE, GLenum quality = GL_DONT_CARE);
void setLineStipple(BOOL stipple = TRUE, GLushort pattern = 0xFFFF, GLint repeat = 1);
void setDepthTest(BOOL test = TRUE, GLclampd depth = 1, GLenum func = GL_LESS);
void createFog(	GLfloat start = 0,
				GLfloat end = 1.0f,
				GLfloat *color = NULL,
				GLfloat density = 1.0f,
				GLuint func = GL_EXP,
				GLenum quality = GL_DONT_CARE);
void setBlend(BOOL blend = TRUE, GLenum source = GL_SRC_ALPHA, GLenum destin = GL_ONE_MINUS_SRC_ALPHA);
void setPolygonOffset(BOOL enable = TRUE, GLfloat factor = 1.0f, GLfloat units = 1.0f);
void setTexturing(BOOL enable);
void fillPolygon(BOOL fill);

}; // namespace Lio

#endif // __LIO3D_H_
